/**
 * 
 */
package org.statewalker.parser.turtle.tokenizer;

import org.statewalker.tokenizer.CharStream;
import org.statewalker.tokenizer.ITokenizer;
import org.statewalker.tokenizer.StreamToken;
import org.statewalker.tokenizer.CharStream.Marker;

/**
 * <pre>
 * [38] character   ::=  '\\u' hex hex hex hex | '\\U' hex hex hex hex hex hex hex hex | '\\\\' | [#x20-#x5B] | [#x5D-#x10FFFF]
 * </pre>
 * 
 * @author kotelnikov
 */
public class CharacterTokenizer implements ITokenizer {

    public static class CharacterReader {

        /**
         * 
         */
        public CharacterReader() {
        }

        private int fromHex(int... code) {
            int result = 0;
            for (int i = code.length - 1; i >= 0; i--) {
                if (code[i] < 0)
                    return -1;
                result <<= 4;
                result |= code[i] & 0xF;
            }
            return result;
        }

        public final int getHexCode(char ch) {
            if (ch >= 0x30 && ch <= 0x39)
                return ch - 0x30;
            if (ch >= 0x41 && ch <= 0x46)
                return (ch - 0x41) + 10;
            return -1;
        }

        public int readNextChar(CharStream stream) {
            int code = -1;
            char ch = stream.getChar();
            if (ch == '\\') {
                stream.incPos();
                ch = stream.getChar();
                if (ch == 'u') {
                    code = fromHex(
                        skipHex(stream),
                        skipHex(stream),
                        skipHex(stream),
                        skipHex(stream));
                } else if (ch == 'U') {
                    code = fromHex(
                        skipHex(stream),
                        skipHex(stream),
                        skipHex(stream),
                        skipHex(stream),
                        skipHex(stream),
                        skipHex(stream),
                        skipHex(stream),
                        skipHex(stream));
                } else if (ch == '\\') {
                    code = '\\';
                }
            } else if ((ch >= 0x20 && ch <= 0x5B)
                || (ch >= 0x5D && ch <= 0x10FFFF)) {
                code = ch;
            }
            return code;
        }

        private int skipHex(CharStream stream) {
            if (stream.incPos())
                return -1;
            char ch = stream.getChar();
            int code = getHexCode(ch);
            return code;
        }

    }

    public static class CharacterToken extends StreamToken {

        private String fValue;

        public CharacterToken(
            String key,
            CharStream stream,
            Marker marker,
            String string) {
            super(key, stream, marker);
            fValue = string;
        }

        public String getValue() {
            return fValue;
        }

    }

    private String fKey;

    private CharacterReader fReader;

    public CharacterTokenizer(String key) {
        this(key, new CharacterReader());
    }

    /**
     * 
     */
    public CharacterTokenizer(String key, CharacterReader reader) {
        fKey = key;
        fReader = reader;
    }

    public StreamToken read(CharStream stream) {
        CharacterToken result = null;
        Marker marker = stream.markPosition();
        try {
            StringBuilder buf = null;
            while (true) {
                int code = fReader.readNextChar(stream);
                if (code < 0) {
                    break;
                }
                if (buf == null) {
                    buf = new StringBuilder();
                }
                buf.append((char) code);
                stream.incPos();
            }
            if (buf != null) {
                result = new CharacterToken(fKey, stream, marker, buf
                    .toString());
            }
            return result;
        } finally {
            marker.close(result == null);
        }
    }

}
